//
//	File: OyDirect3D9_init.cpp
//

#include "OyDirect3D9.h"
#include "OyDirect3D9_SkinManager.h"
#include "OyDirect3D9_VertexCache.h"
#include "OyDirect3D9_MaterialSystem.h"
#include "Font\OyFontManager.h"
#include "..\OyMath3D\OyMath3D.h"
#include "Config\D3D9Config.h"
#include "MaterialSystem\MaterialRenderer.h"
#include "DX9Shader\DX9ShaderDatabase.h"
#include "DX9Shader\DX9AfterEffectManager.h"
#include "Camera\D3D9Camera.h"
#ifdef _DEBUG
#include <vld.h>
#endif

/**
 * DLL Entry Point similar to WinMain()/main()
 */
BOOL WINAPI DllEntryPoint(HINSTANCE hDll,
                          DWORD     fdwReason,
                          LPVOID    lpvReserved) 
{
	switch(fdwReason) 
	{
		// called when we attach to the DLL
		case DLL_PROCESS_ATTACH:
			/* dll init/setup stuff */
			break;
		case DLL_PROCESS_DETACH:
			/* dll shutdown/release stuff */
			break;
		default:
			break;
	};
   
	return TRUE;
}

/**
 * Exported create function: Creates a new OyRenderDevice object.
 */
HRESULT CreateRenderDevice( HINSTANCE hDLL, OyRenderDevice **pDevice ) 
{
	if(!*pDevice) 
	{
		*pDevice = new OyDirect3D9();
		return OY_OK;
	}
	return OY_FAIL;
}

/**
 * Exported release function: Realeses the given OyRenderDevice object.
 */
HRESULT ReleaseRenderDevice( OyRenderDevice **pDevice )
{
	if( !*pDevice ) 
	{
		return OY_FAIL;
	}
	delete *pDevice;
	*pDevice = NULL;
	return OY_OK;
}


/*-----------------------------------------------------------*/
/* OyDirect3D9 class implementation                               *
/*-----------------------------------------------------------*/
OyRenderDevice* OyDirect3D9::m_OyEngineRenderDevice = NULL;

OyDirect3D9::OyDirect3D9()
{
	m_pEnum					= NULL;
	m_pD3D					= NULL;
	m_pDevice				= NULL;

	m_UPOSdecl				= NULL;
	m_UPOSNORCOLTEXdecl		= NULL;
	m_UTEXdecl				= NULL;
	m_UCOLORdecl			= NULL;
	m_ANIMdecl				= NULL;
	m_THREETEXdecl			= NULL;
	m_UTANdecl				= NULL;
	m_FONTdecl				= NULL;

	for( int i=0; i<MAX_3DHWND; i++ )
	{
		m_pChain[i]				= NULL;
		m_pChainDepthSurface[i] = NULL;
	}

	m_ClearColor			= D3DCOLOR_COLORVALUE(0.0f,0.0f,0.0f,1.0f);
	m_bRunning				= false;
	m_bIsSceneRunning		= false;
	m_bStencil				= false;

	m_pSkinManager			= NULL;
	m_pVCManager			= NULL;
	m_pCurrentCamera		= NULL;
	m_pFontManager			= NULL;

	m_nActivehWnd			= 0;
	m_OyEngineRenderDevice	= this;
}

OyDirect3D9::~OyDirect3D9() 
{
	Release();
}

/**
 * Release all the Direct3D resource stuff.
 */
void OyDirect3D9::Release() 
{
	if(m_pEnum) 
	{
		delete m_pEnum;
		m_pEnum = NULL;
	}

	if(m_pSkinManager)
	{
		delete m_pSkinManager;
		m_pSkinManager = NULL;
	}

	if( m_pVCManager )
	{
		delete m_pVCManager;
		m_pVCManager = NULL;
	}

	if( m_pCurrentCamera )
	{
		delete m_pCurrentCamera;
		m_pCurrentCamera = NULL;
	}

	if( m_pFontManager )
	{
		delete m_pFontManager;
		m_pFontManager = NULL;
	}

	DX9GetShaderDatabase()->Shutdown();
	DX9GetAfterEffectManager()->Shutdown();

	if(m_UPOSdecl)
	{
		m_UPOSdecl->Release();
		m_UPOSdecl = NULL;
	}
	if(m_UPOSNORCOLTEXdecl)
	{
		m_UPOSNORCOLTEXdecl->Release();
		m_UPOSNORCOLTEXdecl = NULL;
	}
	if(m_UTEXdecl)
	{
		m_UTEXdecl->Release();
		m_UTEXdecl = NULL;
	}
	if(m_UCOLORdecl)
	{
		m_UCOLORdecl->Release();
		m_UCOLORdecl = NULL;
	}
	if(m_ANIMdecl)
	{
		m_ANIMdecl->Release();
		m_ANIMdecl = NULL;
	}
	if(m_THREETEXdecl)
	{
		m_THREETEXdecl->Release();
		m_THREETEXdecl = NULL;
	}
	if(m_UTANdecl)
	{
		m_UTANdecl->Release();
		m_UTANdecl = NULL;
	}
	if(m_FONTdecl)
	{
		m_FONTdecl->Release();
		m_FONTdecl = NULL;
	}

	for( int i=0; i<MAX_3DHWND; i++ )
	{
		if( m_pChain[i] )
		{
			m_pChain[i]->Release();
			m_pChain[i] = NULL;
		}
		if( m_pChainDepthSurface[i] )
		{
			m_pChainDepthSurface[i]->Release();
			m_pChainDepthSurface[i] = NULL;
		}
	}

	if(m_pDevice) 
	{
		m_pDevice->Release();
		m_pDevice = NULL;
	}
	if(m_pD3D)
	{
		m_pD3D->Release();
		m_pD3D = NULL;
	}
	OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"Release D3D9 Resources...");
}

/* -> IN: HWND      - handle of application window
 *        HWND      - array to render child-window HWNDs or NULL
 *        int       - number of child HWNDs in array
 *        int       - minimum number of depth bits
 *        int       - minimum number of stencil bits
 */
HRESULT OyDirect3D9::Init( HWND hWnd, const HWND *hWnd3D, 
						 int nNumhWnd, int nMinDepth, int nMinStencil )
{
	// are there any child windows to use?
	if(nNumhWnd > 0) 
	{
		if( nNumhWnd > MAX_3DHWND )
			nNumhWnd = MAX_3DHWND;
		memcpy( &m_hWnd[0], hWnd3D, sizeof(HWND)*nNumhWnd );
		m_nNumhWnd = nNumhWnd;
	}
	else // else store handle to main window at least
	{
		m_hWnd[0] = hWnd;
		m_nNumhWnd = 0;
	}
	m_hWndMain = hWnd;

	if( nMinStencil > 0 )
		m_bStencil = true;

	// create enum object
	OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"D3D9 Device Enum...");
	m_pEnum = new OyDirect3D9Enum( nMinDepth, nMinStencil );
	
	if( m_pEnum->Enum() != OY_OK )
	{
		OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"error: D3D9 Device Enum Fail!");
		return OY_FAIL;
	}

	OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"Loading D3D9 Device INI file...");
	if( !m_pEnum->LoadD3DSetting(&m_xDeviceINI, &m_DspmdINI, &m_fmtAINI, &m_fmtBINI, &m_bWindowed, &m_multiSampleINI) )
	{
		OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"error: Read D3D9 Device INI file Fail!");
		return OY_FAIL;
	}

	return Go();
}

HRESULT OyDirect3D9::Go(void)
{
	OyCOMBOINFO_D3DDEVICE xCombo;
	HRESULT hr;
	HWND hwnd;

	if( m_pD3D ) 
	{
		m_pD3D->Release();
		m_pD3D = NULL;
	}
	m_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
	if( !m_pD3D )
	{
		OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"error: Direct3DCreate9 Fail!");
		return OY_CREATEAPI;
	}


	// find the appropriate combo
	for(UINT i=0; i<m_xDeviceINI.nNumCombo; i++) 
	{
		if( (m_xDeviceINI.d3dCombo[i].bWindowed  == m_bWindowed) &&
			(m_xDeviceINI.d3dCombo[i].d3dDevType == m_xDeviceINI.d3dDevType) &&
			(m_xDeviceINI.d3dCombo[i].fmtAdapter == m_fmtAINI) &&
			(m_xDeviceINI.d3dCombo[i].fmtBackBuffer == m_fmtBINI) ) 
		{
			xCombo = m_xDeviceINI.d3dCombo[i];
			break;
		}
	}

	// prepare present parameters structure
	ZeroMemory( &m_d3dpp, sizeof(m_d3dpp) );
	m_d3dpp.Windowed               = m_bWindowed;
	m_d3dpp.BackBufferCount        = 1;
	m_d3dpp.BackBufferFormat       = m_DspmdINI.Format;
	m_d3dpp.EnableAutoDepthStencil = TRUE;
	m_d3dpp.AutoDepthStencilFormat = xCombo.fmtDepthStencil;
	m_d3dpp.MultiSampleType        = (xCombo.msType > m_multiSampleINI) ? m_multiSampleINI : xCombo.msType;
	m_d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;

	// stencil buffer active?
	if( (xCombo.fmtDepthStencil == D3DFMT_D24S8) ||
		(xCombo.fmtDepthStencil == D3DFMT_D24X4S4) ||
		(xCombo.fmtDepthStencil == D3DFMT_D15S1) )
		m_bStencil = true;
	else
		m_bStencil = false;

	// fullscreen mode
	if(!m_bWindowed) 
	{
		m_d3dpp.hDeviceWindow     = hwnd = m_hWndMain;
		m_d3dpp.BackBufferWidth   = m_DspmdINI.Width;
		m_d3dpp.BackBufferHeight  = m_DspmdINI.Height;
		ShowCursor(FALSE);
	}
	// windowed mode
	else
	{
		m_d3dpp.hDeviceWindow     = hwnd = m_hWnd[0];
		m_d3dpp.BackBufferWidth   = GetSystemMetrics(SM_CXSCREEN);
		m_d3dpp.BackBufferHeight  = GetSystemMetrics(SM_CYSCREEN);
	}

	m_dwScreenWidth				  = m_d3dpp.BackBufferWidth;
	m_dwScreenHeight			  = m_d3dpp.BackBufferHeight;

	// create direct3d device
	hr = m_pD3D->CreateDevice( m_xDeviceINI.nAdapter, m_xDeviceINI.d3dDevType,
								hwnd, xCombo.dwBehavior, &m_d3dpp, &m_pDevice );

	if( FAILED(hr) ) 
	{
		OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"error: OyDirect3D9::CreateDevice Fail!");
		return OY_CREATEDEVICE;
	}

	//create additional swap chains
	CreateSwapChains();

	m_bRunning        = true;
	m_bIsSceneRunning = false;

	m_dwWidth         = m_d3dpp.BackBufferWidth;
	m_dwHeight        = m_d3dpp.BackBufferHeight;

	// INIT Shader Version
	GetOyD3D9Config()->SetShaderVersion(xCombo.dwVertexShaderVer, xCombo.dwPixelShaderVer);
	GetOyD3D9Config()->SetVertexShaderConst(xCombo.dwVertexShaderConst);
	GetOyD3D9Config()->SetGlobalAmbientColor(0xFF969696);

	return OnceInit();
}

// create additional swap chains if wished and possible
HRESULT OyDirect3D9::CreateSwapChains(void)
{	
	if( (m_nNumhWnd > 0) && m_bWindowed ) 
	{
		for(UINT i=0; i<m_nNumhWnd; i++) 
		{
			m_d3dpp.hDeviceWindow = m_hWnd[i];

			RECT rcWnd;
			GetClientRect(m_hWnd[i], &rcWnd);
			m_d3dpp.BackBufferWidth   = rcWnd.right - rcWnd.left;
			m_d3dpp.BackBufferHeight  = rcWnd.bottom - rcWnd.top;

			m_pDevice->CreateAdditionalSwapChain(&m_d3dpp, &m_pChain[i]);

			m_pDevice->CreateDepthStencilSurface(m_d3dpp.BackBufferWidth,
				m_d3dpp.BackBufferHeight, m_d3dpp.AutoDepthStencilFormat, 
				D3DMULTISAMPLE_NONE, 0, TRUE, &m_pChainDepthSurface[i], NULL);
		}
	}
	return S_OK;
}

HRESULT OyDirect3D9::OnceInit(void)
{
	// init 3dmath lib
	if( !COyCPU::GetInstance()->OyMath3DInitCPU() )
		OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"no SIMD compatible CPU detected...");
	else
		OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"SIMD compatible CPU detected => using fast math...");

	// init vertex decl
	CreateVertexDeclaration();

	// init Skin Manager
	m_pSkinManager = new OyD3D9SkinManager(m_pDevice);

	// Vertex Cache Manager
	m_pVCManager = new OyD3D9VertexCacheManager(m_pSkinManager, m_pDevice, this);

	// Font Manager
	m_pFontManager = new OyFontManager();
	m_pFontManager->Initialize(m_pSkinManager, m_pDevice, this);

	// Camera System
	m_pCurrentCamera = new OyD3D9Camera();

	// Material system
	OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"loading Game Materials...");
	GetOyMaterialSystem()->LoadGameMaterials();
	OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"Material Render Initialize...");
	GetMaterialRenderer()->Initialize(m_pSkinManager, m_pDevice, this);

	// Shader system
	if( GetOyD3D9Config()->IsEnableShader() )
	{
		OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"Initialize Shaders...");
		DX9GetShaderDatabase()->Initialize(m_pDevice);
		DX9GetAfterEffectManager()->Initialize(m_pDevice);
	}

	OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"one time D3D9 init complete...");

	return OY_OK;
}

HRESULT OyDirect3D9::CreateVertexDeclaration(void)
{
	// VERTEX DECLARATION ///////////////////////////////////////////////////
	D3DVERTEXELEMENT9 OyUPOS_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		D3DDECL_END()
	};

	D3DVERTEXELEMENT9 OyPOSNORCOLTEX_Vertexdecl[] =
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0, 12,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,			0 },
		{ 0, 24,	D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,			0 },
		{ 0, 28,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		D3DDECL_END()
	};

	D3DVERTEXELEMENT9 OyUTEX_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0, 12,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,			0 },
		{ 0, 24,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		D3DDECL_END()
	};
	D3DVERTEXELEMENT9 OyUCOLOR_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0, 12,	D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,			0 },
		{ 0, 16,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		D3DDECL_END()
	};
	D3DVERTEXELEMENT9 OyANIM_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0, 12,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,			0 },
		{ 0, 24,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		{ 0, 32,	D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES,	0 },
		D3DDECL_END()
	};
	D3DVERTEXELEMENT9 OyTHREETEX_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0, 12,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,			0 },
		{ 0, 24,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		{ 0, 32,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		1 },
		{ 0, 40,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		2 },
		D3DDECL_END()
	};
	D3DVERTEXELEMENT9 OyUTAN_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0, 12,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,			0 },
		{ 0, 24,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		{ 0, 32,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,		0 },
		D3DDECL_END()
	};
	D3DVERTEXELEMENT9 OyFONT_VertexDecl[] = 
	{
		{ 0,  0,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT,		0 },
		{ 0, 16,	D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,			0 },
		{ 0, 20,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,		0 },
		D3DDECL_END()
	};

	// create the declarations
	m_pDevice->CreateVertexDeclaration(OyUPOS_VertexDecl,			&m_UPOSdecl);
	m_pDevice->CreateVertexDeclaration(OyPOSNORCOLTEX_Vertexdecl,	&m_UPOSNORCOLTEXdecl);
	m_pDevice->CreateVertexDeclaration(OyUTEX_VertexDecl,			&m_UTEXdecl);
	m_pDevice->CreateVertexDeclaration(OyUCOLOR_VertexDecl,			&m_UCOLORdecl);
	m_pDevice->CreateVertexDeclaration(OyANIM_VertexDecl,			&m_ANIMdecl);
	m_pDevice->CreateVertexDeclaration(OyTHREETEX_VertexDecl,		&m_THREETEXdecl);
	m_pDevice->CreateVertexDeclaration(OyUTAN_VertexDecl,			&m_UTANdecl);
	m_pDevice->CreateVertexDeclaration(OyFONT_VertexDecl,			&m_FONTdecl);
	m_pDevice->SetFVF(NULL);

	return OY_OK;
}

LPDIRECT3DVERTEXDECLARATION9 OyDirect3D9::GetVertexDeclaration(OY_VERTEXTYPE type)
{
	switch( type )
	{
	case VID_UPOS:
		return m_UPOSdecl;
	case VID_UNORCOLTEX:
		return m_UPOSNORCOLTEXdecl; 
	case VID_UTEX:
		return m_UTEXdecl;
	case VID_UCOLOR:
		return m_UCOLORdecl;
	case VID_ANIM:
		return m_ANIMdecl;
	case VID_THREETEX:
		return m_THREETEXdecl;
	case VID_UTAN:	
		return m_UTANdecl;
	case VID_FONT:
		return m_FONTdecl;
	default:
		return NULL;
	}
	return NULL;
}

void OyDirect3D9::OnDeviceLost(void)
{
	if( m_pFontManager )
		m_pFontManager->OnDeviceLost();
	if( m_pVCManager )
		m_pVCManager->OnDeviceLost();

	DX9GetShaderDatabase()->OnDeviceLost();
	DX9GetAfterEffectManager()->OnDeviceLost();

	// destroy additional swap chains
	for( int i=0; i<MAX_3DHWND; i++ )
	{
		if( m_pChain[i] )
		{
			m_pChain[i]->Release();
			m_pChain[i] = NULL;
		}
		if( m_pChainDepthSurface[i] )
		{
			m_pChainDepthSurface[i]->Release();
			m_pChainDepthSurface[i] = NULL;
		}
	}
}

void OyDirect3D9::OnDeviceReset(void)
{
	HRESULT hr;
	hr = m_pDevice->TestCooperativeLevel();

	// Device can be reset now
    if( SUCCEEDED(hr) || hr == D3DERR_DEVICENOTRESET )
    {
		// Release resource allocated as D3DPOOL_DEFAULT
		OnDeviceLost();

        // Reset device
        hr = m_pDevice->Reset( &m_d3dpp ) ;
        if( SUCCEEDED(hr) )
        {
			m_dwScreenWidth	= m_d3dpp.BackBufferWidth;
			m_dwScreenHeight = m_d3dpp.BackBufferHeight;

			{
				if( m_pFontManager )
					m_pFontManager->OnDeviceReset();
				if( m_pVCManager )
					m_pVCManager->OnDeviceReset();

				DX9GetShaderDatabase()->OnDeviceReset();
				DX9GetAfterEffectManager()->OnDeviceReset();

				// Recreate additional swap chains
				CreateSwapChains();

				UseWindow(m_nActivehWnd);
			}

			OyLog_Write_L2(OyXMLLog::Event, OyXMLLog::Engine, L"D3D9 Device Reset Successed...");
        }
		else
		{
			OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"D3D9 Device Reset Failed!");
		}
	}	
    else if( hr == D3DERR_DEVICELOST )
    {
		// Device is still in lost state, wait
        Sleep(25);
    }
    else// Other error, Show error box
    {
        OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"D3D9 Device Reset Failed!");
    }
	return;
}

